#include <Arduino.h>
#include <RadioLib.h>
#include <ArduinoRS485.h> // ArduinoModbus depends on the ArduinoRS485 library
#include <ArduinoModbus.h>

typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;

#define LED_WIFI PB1
#define LED_RF PB0
#define LED_485 PB8
#define LED_485_2 PB9
#define LED_232_M PC14
#define LED_232_S PA1
struct
{
  char channels[8];
  int Serial1_baud, Serial1_config;
  int Serial2_baud, Serial2_config;
  int Serial3_baud, Serial3_config;
  int Serial4_baud, Serial4_config;
  int Serial5_baud, Serial5_config;
  int wt8920, wt297;
  u_int16_t lt8920_channel;
  uint64_t xn297_txaddr, xn297_P0, xn297_P1, xn297_P2, xn297_P3, xn297_P4, xn297_P5;
} retain;
#include <ELClient.h>
#include <ELClientCmd.h>
#include <ELClientMqtt.h>
#include <ELClientSocket.h>
// Initialize a connection to esp-link using the normal hardware serial port both for
// SLIP and for debug messages.
ELClient esp(&Serial1, &Serial1);

// Initialize CMD client (for GetTime)
ELClientCmd elcmd(&esp);

// Initialize the MQTT client
ELClientMqtt mqtt(&esp);

// ELClientSocket ntpUDP(&esp);
bool connected;
// Callback made from esp-link to notify of wifi status changes
// Here we just print something out for grins

SPISettings ss(1000000, MSBFIRST, SPI_MODE0);
SPIClass _SPI3(PB5, PB4, PB3);
#define BOARD_USART2_TX_PIN PA2
#define BOARD_USART2_RX_PIN PA3
SX1278 radio = new Module(PC9, PA15, PC8, RADIOLIB_NC, _SPI3, ss);
DEFINE_USART_RS485(serial2, 2, -1, -1);
#include <ELClientWebServer.h>
ELClientWebServer webServer(&esp);
#include "frame.h"
th01_t dat[8];
// Callback when MQTT is connected
void mqttConnected(void *response)
{
  Serial1.println("MQTT connected!");
  mqtt.subscribe("/esp-link/1");
  mqtt.subscribe("/hello/world/#");
  //mqtt.subscribe("/esp-link/2", 1);
  //mqtt.publish("/esp-link/0", "test1");
}

// Callback when MQTT is disconnected
void mqttDisconnected(void *response)
{
  Serial1.println("MQTT disconnected");
  connected = false;
}

// Callback when an MQTT message arrives for one of our subscriptions
void mqttData(void *response)
{
  ELClientResponse *res = (ELClientResponse *)response;

  Serial1.print("Received: topic=");
  String topic = res->popString();
  Serial1.println(topic);

  Serial1.print("data=");
  String data = res->popString();
  Serial1.println(data);
}

void mqttPublished(void *response)
{
  Serial1.println("MQTT published");
}
String floatToString(float f)
{
  int16_t intg = (int16_t)(f * 100.f);
  int16_t int_part = intg / 100;
  int16_t fract_part = intg % 100;

  char buf[20];
  sprintf(buf, "%d.%02d", int_part, fract_part);
  return buf;
}
String intToString(u16 f)
{
  int16_t int_part = f / 100;
  int16_t fract_part = f % 100;

  char buf[20];
  sprintf(buf, "%d.%02d", int_part, fract_part);
  return buf;
}
String table;
void voltageRefreshCb(char *url)
{
  table = F("[[\"编号\",\"数据\",\"温度\",\"湿度\",\"电池\"]");
  // calculate voltage value
  // String v = floatToString((float)measured_voltage / 256.f);
  // v += " V";
  // webServer.setArgString(F("voltage"), v.begin());

  char buf[20];
  // calculate history table

  for (uint8_t i = 0; i < 8; i++)
  {
    table += F(",[\"");
    sprintf(buf, "%d", dat[i].base.site);
    String v = buf;
    table += v;
    table += F(" \",\"");
    sprintf(buf, "%d", dat[i].index);
    table += buf;
    table += F(" \",\"");
    table += intToString(dat[i].temp);
    table += F(" ­°\",\"");
    table += intToString(dat[i].sht);
    table += F(" ­\",\"");
    table += intToString(dat[i].bat);
    table += F(" V\"]");
  }

  table += ']';
  webServer.setArgJson(F("table"), table.begin());
}
void resetCb(void)
{
  Serial.println("EL-Client (re-)starting!");
  bool ok = false;
  u8 count = 10;
  do
  {
    ok = esp.Sync(); // sync up with esp-link, blocks for up to 2 seconds
    if (!ok)
      Serial.println("EL-Client sync failed!");
  } while (!ok && count--);
  if (ok)
  {
    Serial.println("EL-Client synced!");

    webServer.setup();
    mqtt.connectedCb.attach(mqttConnected);
    mqtt.disconnectedCb.attach(mqttDisconnected);
    mqtt.publishedCb.attach(mqttPublished);
    mqtt.dataCb.attach(mqttData);
    mqtt.setup();
    URLHandler *voltageHandler = webServer.createURLHandler(F("/lora.html.json"));

    voltageHandler->loadCb.attach(voltageRefreshCb);
    voltageHandler->refreshCb.attach(voltageRefreshCb);
  }
}
void wifiCb(void *response)
{
  ELClientResponse *res = (ELClientResponse *)response;
  if (res->argc() == 1)
  {
    uint8_t status;
    res->popArg(&status, 1);

    if (status == STATION_GOT_IP)
    {
      Serial1.println("WIFI CONNECTED");
      connected = true;
    }
    else
    {
      Serial1.print("WIFI NOT READY: ");
      Serial1.println(status);
    }
  }
}

volatile bool receivedFlag = false;

// disable interrupt when it's not needed
volatile bool enableInterrupt = true;

// this function is called when a complete packet
// is received by the module
// IMPORTANT: this function MUST be 'void' type
//            and MUST NOT have any arguments!
void setFlag(void)
{
  // check if the interrupt is enabled
  if (!enableInterrupt)
  {
    return;
  }

  // we got a packet, set the flag
  receivedFlag = true;
}
void sx1278_setup()
{
  // initialize SX1278 FSK modem with default settings
  Serial3.print(F("[SX1278] Initializing ... "));
  int state = radio.begin();
  if (state == ERR_NONE)
  {
    Serial3.println(F("success!"));
  }
  else
  {
    Serial3.print(F("failed, code "));
    Serial3.println(state);
    while (true)
      ;
  }

  // if needed, you can switch between LoRa and FSK modes
  //
  // radio.begin()       start LoRa mode (and disable FSK)
  // radio.beginFSK()    start FSK mode (and disable LoRa)
  radio.setDio0Action(setFlag);
}
void load_default()
{
  for (int i = 0; i < 8; i++)
    retain.channels[i] = 0;
  retain.Serial1_baud = 115200;
  retain.Serial1_config = SERIAL_8E1;
  retain.Serial2_baud = 9600;
  retain.Serial2_config = SERIAL_8N1;
  retain.Serial3_baud = 9600;
  retain.Serial3_config = SERIAL_8N1;
  retain.Serial4_baud = 9600;
  retain.Serial4_config = SERIAL_8N1;
  retain.Serial5_baud = 9600;
  retain.Serial5_config = SERIAL_8N1;
  retain.wt297 = 20;
  retain.wt8920 = 20;
  retain.lt8920_channel = 50;
}
void sx1278_send()
{
  Serial3.print(F("[SX1278] Transmitting packet ... "));

  // you can transmit C-string or Arduino string up to
  // 256 characters long
  // NOTE: transmit() is a blocking method!
  //       See example SX127x_Transmit_Interrupt for details
  //       on non-blocking transmission method.
  int state = radio.transmit("Hello World!");

  // you can also transmit byte array up to 256 bytes long
  /*
    byte byteArr[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
    int state = radio.transmit(byteArr, 8);
  */

  if (state == ERR_NONE)
  {
    // the packet was successfully transmitted
    Serial3.println(F(" success!"));

    // print measured data rate
    Serial3.print(F("[SX1278] Datarate:\t"));
    Serial3.print(radio.getDataRate());
    Serial3.println(F(" bps"));
  }
  else if (state == ERR_PACKET_TOO_LONG)
  {
    // the supplied packet was longer than 256 bytes
    Serial3.println(F("too long!"));
  }
  else if (state == ERR_TX_TIMEOUT)
  {
    // timeout occurred while transmitting packet
    Serial3.println(F("timeout!"));
  }
  else
  {
    // some other error occurred
    Serial3.print(F("failed, code "));
    Serial3.println(state);
  }

  // wait for a second before transmitting again
  delay(1000);
}
uint8_t rec[64];
u16 swap(u16 v)
{
  u8 a = v >> 8;
  u8 b = v & 0xff;
  u16 c = (b << 8) + a;
  return c;
}
void sx1278_recv()
{
  // Serial.print(F("[SX1278] Waiting for incoming transmission ... "));

  // you can receive data as an Arduino String
  // NOTE: receive() is a blocking method!
  //       See example ReceiveInterrupt for details
  //       on non-blocking reception method.
  // String str;
  int state = radio.readData(rec, 64);
  // you can also receive data as byte array
  /*
    byte byteArr[8];
    int state = radio.receive(byteArr, 8);
  */

  if (state == ERR_NONE)
  {
    // packet was successfully received
    // Serial3.println(F("success!"));

    // print the data of the packet
    // Serial3.print(F("[SX1278] Data:\t\t\t"));
    mqtt.publish("/iot/th01", rec, 16);
    th01_t *d = (th01_t *)rec;
    if (swap(d->base.swName) == 0x9802)
    {
      d->base.site = 2;
      dat[d->base.site].base.site = d->base.site;
      dat[d->base.site].bat = swap(d->bat);
      dat[d->base.site].index = swap(d->index);
      dat[d->base.site].temp = swap(d->temp);
      dat[d->base.site].sht = swap(d->sht);
      dat[d->base.site].base.build = swap(d->base.build);
      dat[d->base.site].base.interval = swap(d->base.interval);
    }
    Serial3.write(rec, 16);
    // Serial3.println(str);

    // print the RSSI (Received Signal Strength Indicator)
    // of the last received packet
    // Serial3.print(F("[SX1278] RSSI:\t\t\t"));
    // Serial3.print(radio.getRSSI());
    // Serial3.println(F(" dBm"));

    // print the SNR (Signal-to-Noise Ratio)
    // of the last received packet
    // Serial3.print(F("[SX1278] SNR:\t\t\t"));
    // Serial3.print(radio.getSNR());
    // Serial3.println(F(" dB"));

    // print frequency error
    // of the last received packet
    // Serial3.print(F("[SX1278] Frequency error:\t"));
    // Serial3.print(radio.getFrequencyError());
    // Serial3.println(F(" Hz"));
  }
  else if (state == ERR_RX_TIMEOUT)
  {
    // timeout occurred while waiting for a packet
    // Serial3.println(F("timeout!"));
  }
  else if (state == ERR_CRC_MISMATCH)
  {
    // packet was received, but is malformed
    Serial3.println(F("CRC error!"));
  }
  else
  {
    // some other error occurred
    Serial3.print(F("failed, code "));
    Serial3.println(state);
  }
}

void setup()
{
  load_default();
  Serial3.begin(9600);
  Serial1.begin(retain.Serial1_baud, retain.Serial1_config); //WIFI
  // Serial2.begin(retain.Serial2_baud, retain.Serial2_config); //232Master
  Serial3.begin(retain.Serial3_baud, retain.Serial3_config); //232 slave

  pinMode(LED_WIFI, OUTPUT);
  pinMode(LED_RF, OUTPUT);
  pinMode(LED_485, OUTPUT);
  pinMode(LED_485_2, OUTPUT);
  pinMode(LED_232_M, OUTPUT);
  pinMode(LED_232_S, OUTPUT);

  digitalWrite(LED_WIFI, 1);

  esp.wifiCb.attach(wifiCb); // wifi status change callback, optional (delete if not desired)
  esp.resetCb = resetCb;
  resetCb();

  sx1278_setup();
  for (int i = 0; i < 0x42; i++)
  {
    int r = radio._mod->SPIgetRegValue(i);
    Serial3.print(i, 16);
    Serial3.print("=");
    Serial3.println(r, 16);
  }
  u16 state = radio.startReceive();
  // RADIOLIB_ASSERT(state);
  if (!ModbusRTUServer.begin(&serial2, 1, 9600))
  {
    //Serial.println("Failed to start Modbus RTU Server!");
    while (1)
      ;
  }
  ModbusRTUServer.configureHoldingRegisters(0x00, sizeof(dat));
  interrupts();
}
static int count;
static uint32_t last;
void loop()
{
  if (receivedFlag)
  {
    // disable the interrupt service routine while
    // processing the data
    enableInterrupt = false;

    // reset flag
    receivedFlag = false;
    sx1278_recv();
    u16 state = radio.startReceive();
    enableInterrupt = true;
  }
  // sx1278_send();
  static int step = 0;
  esp.Process();

  if (connected && (millis() - last) > 4000)
  {
    Serial1.println("publishing");
    char buf[12];

    itoa(count++, buf, 10);
    // mqtt.publish("/esp-link/1", buf);

    itoa(count + 99, buf, 10);
    // mqtt.publish("/hello/world/arduino", buf);

    // uint32_t t = elcmd.GetTime();
    // Serial3.print("Time: ");
    // Serial3.println(t);
    last = millis();
  }

  switch (step)
  {
  case 5:
    step = 0;
    break;
  default:
    step++;
  }
  digitalWrite(LED_WIFI, step == 0);
  digitalWrite(LED_RF, step == 1);
  digitalWrite(LED_485, step == 2);
  digitalWrite(LED_485_2, step == 3);
  digitalWrite(LED_232_M, step == 4);
  digitalWrite(LED_232_S, step == 5);
  ModbusRTUServer.poll(); //
  for (int i = 0; i < sizeof(dat); i++)
  {
    u16 *v = (u16 *)&dat + i;
    ModbusRTUServer.holdingRegisterWrite(i, *v); //
  }
}